package com.websocket.service.adapter;

import com.websocket.MessageData;
import com.websocket.WebSocketConfig;
import com.websocket.annotation.SendTo;
import com.websocket.annotation.WsParam;
import com.websocket.converter.MessageConverter;
import com.websocket.exception.MissingMessageParameterException;
import com.websocket.service.WebSocketConnection;
import com.websocket.service.mapping.HandlerExecution;
import com.websocket.service.WebSocketTemplate;
import org.java_websocket.WebSocket;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.security.Principal;
import java.util.Map;
import java.util.Objects;

public class WebSocketHandlerAdapter {

    private final MessageConverter converter;
    private final WebSocketTemplate template;

    public WebSocketHandlerAdapter(WebSocketConfig config, WebSocketTemplate template) {
        this.converter = config.converter;
        this.template = template;
    }

    public void handle(HandlerExecution handler, MessageData messageData, WebSocket conn, Principal user) {
        WebSocketConnection connection = new WebSocketConnection(conn);
        // 执行拦截器
        boolean interceptorResult = doInterceptors(handler, messageData, connection, user);
        if (!interceptorResult) return;
        // 映射参数
        Object[] injectArgs = resolveArgument(handler.getMethod().getMethod(), messageData, connection, user);

        // 执行 handler
        Object returnData;
        try {
            returnData = handler.getMethod().invoke(injectArgs);
        } catch (InvocationTargetException  e) {
            throw new com.websocket.exception.InvocationTargetException(e);
        } catch (IllegalAccessException e) {
            throw new com.websocket.exception.IllegalAccessException(e);
        }

        // 处理返回结果
        handleReturnData(returnData, handler.getMethod().getMethod(), conn);
    }

    private void handleReturnData(Object returnData, Method method, WebSocket conn) {
        if (returnData == null) return;

        SendTo anno = method.getAnnotation(SendTo.class);
        if (anno == null) return;

        if (anno.broadcast()) {
            template.sendToAll(returnData);
        } else {
            template.sendTo(conn, returnData);
        }
    }

    private boolean doInterceptors(HandlerExecution handler, MessageData messageData, WebSocketConnection conn, Principal user) {
        return handler.getExecutionChain().applyHandle(conn, messageData.getData(), handler.getMethod().getMethod(), user);
    }

    /**
     * @return 执行 handler 所需的参数
     */
    private Object[] resolveArgument(Method method, MessageData messageData, WebSocketConnection conn, Principal user) {
        Map<String, Object> actualArgs = messageData.getData();
        Parameter[] formalArgs = method.getParameters();
        Object[] injectArgs = new Object[formalArgs.length];

        // 优先注入 WebSocket 和 Principal 实例
        int[] injectResult = injectConnAndUser(injectArgs, formalArgs, conn, user);
        int injectCount = injectResult[0];

        // 没有其他参数可注入
        if (injectArgs.length == injectCount) return injectArgs;

        // 只有一个其他参数
        if (injectArgs.length - injectCount == 1) {
            // 是否使用该参数包含了所有的 message 参数
            int theParamIndex = injectResult[1];
            Parameter theParam = formalArgs[theParamIndex];
            String paramName = getParamName(theParam);
            Object actualArg = actualArgs.get(paramName);
            // 形参名称在请求参数中没有对应的值 => 将整个请求参数转为形参实例
            // 否则 => 将对应的值转为形参实例
            injectArgs[theParamIndex] = converter.reParse(Objects.requireNonNullElse(actualArg, actualArgs), theParam);
            return injectArgs;
        }

        // 解析其他参数
        for (int i = 0; i < formalArgs.length; i++) {
            // 跳过之前注入的 Socket 和 Principal
            if (injectArgs[i] != null) continue;

            Parameter formalArg = formalArgs[i];
            // 参数的名称在请求参数中没有对应的数据，若参数为必需，则报异常，否则注入null
            WsParam anno = formalArg.getAnnotation(WsParam.class);
            String paramName = getParamName(formalArg, anno);
            Object actualArg = actualArgs.get(paramName);
            if (actualArg == null) {
                if (anno != null && anno.required())
                    throw new MissingMessageParameterException(formalArg);
                injectArgs[i] = null;
            } else {
                injectArgs[i] = converter.reParse(actualArg, formalArg);
            }

        }
        return injectArgs;
    }


    /**
     * 注入 Socket 连接对象和 Principal
     *
     * @return [注入成功的个数，除开以上两个参数的第一个参数下标]
     */
    private int[] injectConnAndUser(Object[] injectArgs, Parameter[] formalArgs, WebSocketConnection conn, Principal user) {
        int injectCount = 0;
        int firstOtherParameterIndex = -1;
        for (int i = 0; i < formalArgs.length; i++) {
            Parameter formalArg = formalArgs[i];
            Class<?> formalArgType = formalArg.getType();
            if (WebSocketConnection.class.isAssignableFrom(formalArgType)) {
                injectArgs[i] = conn;
                injectCount++;
            } else if (Principal.class.isAssignableFrom(formalArgType)) {
                injectArgs[i] = user;
                injectCount++;
            } else {
                if (firstOtherParameterIndex == -1)
                    firstOtherParameterIndex = i;
            }
        }
        return new int[]{injectCount, firstOtherParameterIndex};
    }

    private String getParamName(Parameter formalArg) {
        WsParam anno = formalArg.getAnnotation(WsParam.class);
        return getParamName(formalArg, anno);
    }


    private String getParamName(Parameter formalArg, WsParam anno) {
        if (anno == null) return formalArg.getName();
        return StringUtils.hasText(anno.value()) ? anno.value() : formalArg.getName();
    }
}
